package raft

import (
	"math/rand"
	"sync"
	"sync/atomic"
)

// must be used within critical section
func (rf *Raft) Convert2Candidate(term int) {
	rf.Role = CANDIDATE
	rf.currentTerm = term
	rf.electionTimeout = rand.Int63n(200) + 450
	DPrintf("node[%d]: Convert To Candidate ,term: %d", rf.me, term)
}

//这一段计数的部分写的不太行，应该直接用context重写
func (rf *Raft) DoElection() {
	rf.mu.Lock()
	if rf.Role != CANDIDATE {
		rf.mu.Unlock()
		return
	}
	rf.currentTerm++
	rf.votedFor = rf.me
	rf.electionTimeStamp = rf.GetNow()
	LastIndex, LastTerm := rf.LastestEntryInfo()
	rf.persist()
	DPrintf("node[%d]: Do election ,term: %d", rf.me, rf.currentTerm)

	mutex := sync.Mutex{}
	cond := sync.NewCond(&mutex)
	counter := &electionCounter{1, 1}
	for i := 0; i < len(rf.peers); i++ {
		if i == rf.me {
			continue //do not send to itself
		}
		go func(idx int) {
			defer cond.Broadcast()
			defer atomic.AddInt32(&counter.all_count, 1)
			rpc_args := RequestVoteArgs{rf.currentTerm, rf.me, LastIndex, LastTerm}
			rpc_reply := RequestVoteReply{}
			got_reply := rf.sendRequestVote(idx, &rpc_args, &rpc_reply)

			if !got_reply {
				return
			}
			rf.mu.Lock()
			defer rf.mu.Unlock()
			if rf.currentTerm == rpc_args.Term {
				if rf.Role != CANDIDATE {
					return
				}
				if rpc_reply.Term > rf.currentTerm {
					rf.Convert2Follower(rpc_reply.Term)
					return
				}
				if rpc_reply.VoteGranted {
					atomic.AddInt32(&counter.success_count, 1)
				}
			}
		}(i)
	}
	rf.mu.Unlock()

	cond.L.Lock()
	defer cond.L.Unlock()
	for int(atomic.LoadInt32(&counter.success_count)) <= (len(rf.peers) / 2) {
		if int(atomic.LoadInt32(&counter.all_count)) == len(rf.peers) {
			return
		}
		cond.Wait()
	}
	rf.mu.Lock()
	defer rf.mu.Unlock()
	if rf.Role != CANDIDATE {
		return
	}
	DPrintf("node[%d]: election over, got majority term: %d", rf.me, rf.currentTerm)
	rf.Convert2Leader(rf.currentTerm)
}

func (rf *Raft) doCandidate() {
	if rf.killed() {
		return
	}
	rf.mu.Lock()
	defer rf.mu.Unlock()
	if rf.Role != CANDIDATE {
		return
	}
	if rf.IsTimeOut(rf.electionTimeStamp, rf.electionTimeout) {
		rf.electionTimeStamp = rf.GetNow()
		go rf.DoElection()
	}
}
